package com.am.server.api.system.permission.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.am.server.api.system.permission.config.model.Menu;
import com.am.server.api.system.permission.config.model.Meta;
import com.am.server.api.system.permission.dao.cache.MenuCacheDao;
import com.am.server.api.system.permission.dao.cache.SystemPermissionCacheDao;
import com.am.server.api.system.permission.exception.LoadMenuFailException;
import com.am.server.api.system.permission.model.entity.SystemPermissionCacheEntity;
import com.am.server.api.system.permission.model.enumerate.MenuType;
import com.am.server.api.system.permission.service.SystemPermissionService;
import com.am.server.common.util.tree.TreeUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import org.xml.sax.SAXException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;

/**
 * @author 阮雪峰
 */
@Service
public class SystemPermissionServiceImpl implements SystemPermissionService {

    private final SystemPermissionCacheDao systemPermissionCacheDao;
    private final MenuCacheDao menuCacheDao;

    public SystemPermissionServiceImpl(SystemPermissionCacheDao systemPermissionCacheDao, MenuCacheDao menuCacheDao) {
        this.systemPermissionCacheDao = systemPermissionCacheDao;
        this.menuCacheDao = menuCacheDao;
    }

    @Override
    public void loadPermissionToCache() {
        load();
    }

    /**
     * 生成权限缓存相关数据
     *
     * @return SystemPermissionCacheDo
     */
    private SystemPermissionCacheEntity load() {
        List<Menu> list = parseConfig();
        // 平铺数据保存
        List<Menu> menuList = menuTreeToList(list);
        // 重新生成菜单树
        List<Menu> menuTree = TreeUtils.build(menuTreeToList(list).stream().filter(item -> !MenuType.ACTION.equals(item.getType())).toList());
        // 所有的key
        List<String> codeList = menuList.stream().map(Menu::getKey).flatMap(Collection::stream).distinct().toList();

        SystemPermissionCacheEntity systemPermissionCacheEntity = new SystemPermissionCacheEntity(list, menuTree, menuList, codeList);
        systemPermissionCacheDao.save(systemPermissionCacheEntity);

        menuCacheDao.saveAll(menuList);

        return systemPermissionCacheEntity;
    }

    private List<Menu> parseConfig() {
        Resource resource = new ClassPathResource("permission/config.xml");
        SAXReader reader = new SAXReader();
        try {
            reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
            reader.setFeature("http://xml.org/sax/features/external-general-entities", false);
            reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
            Document document = reader.read(resource.getInputStream());
            Element root = document.getRootElement();
            return parseMenuList(root, null);
        } catch (DocumentException | IOException | SAXException e) {
            throw new LoadMenuFailException(e);
        }

    }


    /**
     * 解析Menu节点
     *
     * @param element element
     * @return List<Menu>
     */
    private List<Menu> parseMenuList(Element element, Menu parent) {
        List<Menu> list = new ArrayList<>(8);
        for (Element menuElement : element.elements("Menu")) {
            Menu menu = parseMenu(menuElement);
            if (parent != null) {
                menu.setParentId(parent.getId());
                menu.setFullPath(String.format("%s/%s", parent.getFullPath(), menu.getPath()));
            }
            list.add(menu);
        }
        return list;
    }

    /**
     * Menu节点转实体
     *
     * @param menuElement menuElement
     * @return Menu
     */
    private Menu parseMenu(Element menuElement) {
        Menu menu = new Menu();
        menu.setId(menuElement.attributeValue("id"));
        menu.setType(MenuType.MENU);
        menu.setName(menuElement.attributeValue("name"));
        menu.setPath(menuElement.attributeValue("path"));
        menu.setFullPath(menuElement.attributeValue("path"));
        menu.setComponent(menuElement.attributeValue("component"));
        menu.setHideMenu(Optional.ofNullable(menuElement.attributeValue("hideMenu")).map(Boolean::parseBoolean).orElse(null));
        menu.setRedirect(menuElement.attributeValue("redirect"));

        Meta meta = parseMeta(menuElement);
        List<Menu> children = new ArrayList<>();
        List<Menu> menuList = parseMenuList(menuElement, menu);
        if (!menuList.isEmpty()) {
            children.addAll(menuList);
        }
        List<Menu> actionList = parseActionList(menuElement, menu);
        if (!actionList.isEmpty()) {
            children.addAll(actionList);
        }
        menu.setMeta(meta);
        menu.setChildren(children);
        return menu;
    }

    /**
     * 解析Action节点
     *
     * @param element element
     * @param parent  parent
     * @return List<Menu>
     */
    private List<Menu> parseActionList(Element element, Menu parent) {
        List<Menu> list = new ArrayList<>(8);
        for (Element actionElement : element.elements("Action")) {
            Menu action = parseAction(actionElement);
            action.setParentId(parent.getId());
            list.add(action);
        }
        return list;
    }

    /**
     * 解析Action节点
     *
     * @param actionElement actionElement
     * @return Menu
     */
    private Menu parseAction(Element actionElement) {
        Meta meta = parseMeta(actionElement);
        List<String> key = parseKeys(actionElement);
        Menu action = new Menu();
        action.setId(actionElement.attributeValue("id"));
        action.setType(MenuType.ACTION);
        action.setName(actionElement.attributeValue("name"));
        action.setMeta(meta);
        action.setKey(key);
        return action;
    }

    /**
     * 解析Key节点
     *
     * @param actionElement actionElement
     * @return List<String>
     */
    private List<String> parseKeys(Element actionElement) {
        List<String> keys = new ArrayList<>(8);
        for (Element element : actionElement.elements("Key")) {
            keys.add(element.attributeValue("id"));
        }
        return keys;
    }

    /**
     * 解析Meta数据
     *
     * @param element element
     * @return Meta
     */
    private Meta parseMeta(Element element) {
        Element metaElement = element.element("Meta");
        return new Meta()
                .setTitle(metaElement.attributeValue("title"))
                .setAffix(Optional.ofNullable(metaElement.attributeValue("affix")).map(Boolean::parseBoolean).orElse(null))
                .setIcon(metaElement.attributeValue("icon"))
                .setIgnoreAuth(Optional.ofNullable(metaElement.attributeValue("ignoreAuth")).map(Boolean::parseBoolean).orElse(null));

    }

    /**
     * 重加缓存权限数据
     *
     * @return SystemPermissionCacheDo
     */
    private SystemPermissionCacheEntity reloadPermissionToCacheAndReturn() {
        return load();
    }

    /**
     * 树形菜单转平铺list
     *
     * @param menuTreeList menuTreeList
     * @return List<Menu>
     */
    private List<Menu> menuTreeToList(List<Menu> menuTreeList) {
        List<Menu> menuList = new ArrayList<>(32);
        transform(menuList, menuTreeList);
        return menuList;
    }

    /**
     * 树形菜单转平铺list
     *
     * @param menuList     结果集
     * @param menuTreeList 菜单树
     */
    private void transform(List<Menu> menuList, List<Menu> menuTreeList) {
        for (Menu menu : menuTreeList) {
            Menu target = new Menu();
            BeanUtil.copyProperties(menu, target, "children");
            menuList.add(target);
            if (!menu.hasChildren()) {
                continue;
            }
            transform(menuList, menu.getChildren());
        }
    }

    @Override
    public List<Menu> getAllTree() {
        return systemPermissionCacheDao.get().map(SystemPermissionCacheEntity::getAllTree).orElseGet(() -> reloadPermissionToCacheAndReturn().getAllTree());
    }

    @Override
    public List<Menu> getMenuTree() {
        return systemPermissionCacheDao.get().map(SystemPermissionCacheEntity::getMenuTree).orElseGet(() -> reloadPermissionToCacheAndReturn().getMenuTree());
    }

    @Override
    public List<Menu> getMenuList() {
        return systemPermissionCacheDao.get().map(SystemPermissionCacheEntity::getMenuList).orElseGet(() -> reloadPermissionToCacheAndReturn().getMenuList());
    }

    @Override
    public List<String> getAllKeyList() {
        return systemPermissionCacheDao.get().map(SystemPermissionCacheEntity::getKeyList).orElseGet(() -> reloadPermissionToCacheAndReturn().getKeyList());
    }
}
